package com.example.gulimall.ware.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.common.config.rabbit.common.RabbitTemplateWrapper;
import com.example.common.config.rabbit.ware.WareTransactionalConfig;
import com.example.common.constant.order.OrderStatusEnum;
import com.example.common.constant.WareConstant;
import com.example.common.entity.order.OrderEntity;
import com.example.common.entity.ware.WareOrderTaskDetailEntity;
import com.example.common.exception.NoStockException;
import com.example.common.exception.UnlockStockException;
import com.example.common.to.ware.SkuHasStockTo;
import com.example.common.to.ware.WareSkuLockTo;
import com.example.common.utils.JacksonUtils;
import com.example.common.utils.PageUtils;
import com.example.common.utils.Query;
import com.example.common.utils.R;
import com.example.common.vo.cart.CartItemVo;
import com.example.common.vo.ware.SkuWareHasStockVo;
import com.example.gulimall.ware.dao.WareSkuDao;
import com.example.gulimall.ware.entity.WareOrderTaskEntity;
import com.example.gulimall.ware.entity.WareSkuEntity;
import com.example.gulimall.ware.feign.OrderFeignService;
import com.example.gulimall.ware.service.WareOrderTaskDetailService;
import com.example.gulimall.ware.service.WareOrderTaskService;
import com.example.gulimall.ware.service.WareSkuService;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;


@Slf4j
@Service("wareSkuService")
public class WareSkuServiceImpl extends ServiceImpl<WareSkuDao, WareSkuEntity> implements WareSkuService {

    @Autowired private OrderFeignService orderFeignService;
    @Autowired private WareOrderTaskService wareOrderTaskService;
    @Autowired private RabbitTemplateWrapper rabbitTemplateWrapper;
    @Autowired private WareOrderTaskDetailService wareOrderTaskDetailService;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {

        String skuId = (String) params.get("skuId");
        String wareId = (String) params.get("wareId");
        final String _default = "0";

        IPage<WareSkuEntity> page = this.page(
                new Query<WareSkuEntity>().getPage(params),
                Wrappers.<WareSkuEntity>lambdaQuery()
                        .eq(
                                StringUtils.isNotBlank(skuId) && !_default.equalsIgnoreCase(skuId),
                                WareSkuEntity::getSkuId, skuId
                        ).eq(
                        StringUtils.isNotBlank(wareId) && !_default.equalsIgnoreCase(wareId),
                        WareSkuEntity::getWareId, wareId
                )
        );

        return new PageUtils(page);
    }

    @Override
    public void updateBatchByCondition(List<WareSkuEntity> success) {

        // 如果不存在库存，那么先新增
        List<WareSkuEntity> insertList = new ArrayList<>();
        List<WareSkuEntity> updateList = new ArrayList<>();

        for (WareSkuEntity entity : success) {
            Integer integer = this.baseMapper.selectCount(
                    Wrappers.<WareSkuEntity>lambdaQuery()
                            .eq(WareSkuEntity::getSkuId, entity.getSkuId())
                            .eq(WareSkuEntity::getWareId, entity.getWareId())
            );
            if (integer > 0) {
                updateList.add(entity);
                continue;
            }
            insertList.add(entity);
        }

        if (CollectionUtils.isNotEmpty(insertList)) {
            this.saveBatch(insertList);
        }
        if (CollectionUtils.isNotEmpty(updateList)) {
            this.baseMapper.updateBatchByCondition(insertList);
        }
    }

    @Override
    public List<SkuHasStockTo> hasStock(List<Long> skuIds) {

        if (CollectionUtils.isEmpty(skuIds)) {
            return Collections.emptyList();
        }

        List<SkuHasStockTo> hasStockTos = this.baseMapper.selectStock(skuIds);
        if (CollectionUtils.isEmpty(hasStockTos)) {
            return Collections.emptyList();
        }

        return hasStockTos.stream().peek(a -> a.setHasStock(a.getStock() > 0)).collect(Collectors.toList());
    }

    /**
     * 解锁订单的条件：
     * 1. 下单成功但订单过期自动取消或被用户手动取消
     * 2. 下订单成功，但是其他业务调用失败，导致订单回滚
     *
     * @param to 提交库存锁定参数
     * @throws Exception 异常
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void lockWare(WareSkuLockTo to) throws Exception {
        try {
            // wms_ware_sku 表
            Long taskId = this.saveOrderTask(to);

            // 1. 锁定仓库，就近原则 (不实现)
            // 2. 每个商品都在哪个仓库
            final Set<Long> skuIdSet = to.getLocks().stream().map(CartItemVo::getSkuId)
                    .collect(Collectors.toSet());

            // 查询出所有的库存，来计算每一个
            final List<WareSkuEntity> wareSkuEntities = this.baseMapper.selectList(
                    Wrappers.<WareSkuEntity>lambdaQuery()
                            .in(CollectionUtil.isNotEmpty(skuIdSet), WareSkuEntity::getSkuId, skuIdSet)
            );
            final Map<Long, WareSkuEntity> wareMap = wareSkuEntities.stream()
                    .collect(Collectors.toMap(WareSkuEntity::getSkuId, Function.identity()));

            final List<SkuWareHasStockVo> stocks = to.getLocks().stream().map(
                    a -> new SkuWareHasStockVo()
                            .setSkuId(a.getSkuId())
                            .setCount(a.getCount())
                            .setWareId(wareMap.get(a.getSkuId()).getWareId())
            ).collect(Collectors.toList());

            // 3. 锁定库存，每一个物品锁定多少件，这里为了去掉 for 是用了先更新，然后再查询的方式

            // 3.1 锁定之前查一次，记录更新前的状态
            List<Long> wareIds = stocks.stream().map(SkuWareHasStockVo::getWareId)
                    .collect(Collectors.toList());
            List<WareSkuEntity> preCacheStock = cacheStock(wareIds, skuIdSet);

            // 3.2 执行锁定操作
            this.doLockStocks(stocks);

            // 3.3 再次查询锁定情况，对比 3.1 得到返回结果
            List<WareSkuEntity> postCacheStock = cacheStock(wareIds, skuIdSet);

            // 4. 验证更新结果
            this.validationStock(preCacheStock, postCacheStock, taskId, to.getOrderSn());
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * 保存订单工作单
     *
     * @param to 订单信息
     */
    private Long saveOrderTask(WareSkuLockTo to) {
        WareOrderTaskEntity entity = new WareOrderTaskEntity()
                .setOrderSn(to.getOrderSn());
        this.wareOrderTaskService.save(entity);
        return entity.getId();
    }

    /**
     * 抽取公共查询，主要是更新前对库存查询与更新后对库存查询
     *
     * @param wareIds  库存
     * @param skuIdSet 商品ID
     * @return 库存信息结果
     */
    private List<WareSkuEntity> cacheStock(List<Long> wareIds, Set<Long> skuIdSet) {
        return this.baseMapper.selectList(
                Wrappers.<WareSkuEntity>lambdaQuery()
                        .in(WareSkuEntity::getWareId, wareIds)
                        .in(WareSkuEntity::getSkuId, skuIdSet)
        );
    }

    /**
     * 对比更新前与更新后的结果
     *
     * @param pre  更新前的库存
     * @param post 更新后的库存
     */
    private void validationStock(List<WareSkuEntity> pre,
                                 List<WareSkuEntity> post,
                                 Long taskId, String orderSn) {
        // 这里更新前的结果与更新后的结果条数一定是一致的
        Map<Long, WareSkuEntity> preMap = pre.stream()
                .collect(Collectors.toMap(WareSkuEntity::getSkuId, Function.identity()));

        List<WareOrderTaskDetailEntity> details = new ArrayList<>();

        for (WareSkuEntity postSkuEntity : post) {
            WareSkuEntity preSkuEntity = preMap.get(postSkuEntity.getSkuId());

            // 计算前后差值，即可得到被更新的数量
            int lockedNum = postSkuEntity.getStockLocked() - preSkuEntity.getStockLocked();
            // 锁定库存失败
            if (lockedNum == 0) {
                throw new NoStockException(postSkuEntity.getSkuName());
            }

            this.gatherTaskDetails(details, postSkuEntity, taskId, orderSn);
        }
        this.saveTaskDetailsAndSend(details);
    }

    /**
     * 收集工作单详情并且发送到MQ
     * 在验证库存锁定情况时候，只要有一个锁定失败，那么
     * 就会抛出异常，但是锁定库存的消息已经发送出去了
     *
     * @param sku       商品信息
     * @param taskId    关联工作单ID
     * @param container 储存容器
     */
    private void gatherTaskDetails(List<WareOrderTaskDetailEntity> container,
                                   WareSkuEntity sku, Long taskId, String orderSn) {
        WareOrderTaskDetailEntity e = new WareOrderTaskDetailEntity()
                .setTaskId(taskId)
                .setOrderSn(orderSn)
                .setSkuId(sku.getSkuId())
                .setWareId(sku.getWareId())
                .setSkuName(sku.getSkuName())
                .setSkuNum(sku.getStockLocked())
                .setLockStatus(WareConstant.StockStatus.LOCKED.getCode());
        container.add(e);
    }

    /**
     * 向MQ批量发送消息
     * 监听此处消息为 {@see StockReleaseListener#handleStockRelease(java.util.List,
     *com.rabbitmq.client.Channel,
     *org.springframework.amqp.core.Message)}
     *
     * @param container 容器
     */
    private void saveTaskDetailsAndSend(List<WareOrderTaskDetailEntity> container) {
        // 先插入，然后再看看是否返回ID
        this.wareOrderTaskDetailService.saveBatch(container);

        this.rabbitTemplateWrapper.sendAndSaveAsync(WareTransactionalConfig.STOCK_EXCHANGE,
                WareTransactionalConfig.DELAY_KEY, container);
    }

    /**
     * 执行锁定库存
     *
     * @param stocks        锁定库存信息
     */
    private void doLockStocks (List<SkuWareHasStockVo> stocks){
        for (SkuWareHasStockVo stock : stocks) {
            this.baseMapper.doLockStock (stock);
            log.info("执行锁定库存： {}", stock);
        }
    }

    @Override
    public void unlockStock(List<LinkedHashMap<String, Object>> container) {

        if (CollectionUtil.isEmpty(container)) {
            log.error("主动消费 - 接收到MQ消息为空：{}", container);
            throw new UnlockStockException("接收到MQ消息为空");
        }

        // 根据订单号，查询所有的订单，逻辑上是只有一个订单的
        List<String> orderSnList = container.stream().map(a -> String.valueOf(a.get("orderSn")))
                .distinct()
                .collect(Collectors.toList());

        if (CollectionUtil.isEmpty(orderSnList)) {
            log.error("解锁库存功能，接收到MQ消息订单号为空：{}", orderSnList);
            throw new UnlockStockException("解锁库存功能，接收到MQ消息订单号为空");
        }

        if (orderSnList.size() > 1) {
            log.error("解锁库存功能，接收到MQ消息订单号为多个：{}", orderSnList);
            throw new UnlockStockException("出现多个订单号: [ " + orderSnList + " ]");
        }

        R r = this.orderFeignService.getOrderStatus(orderSnList.get(0));

        if (r.getCode() == R.DEFAULT_CODE) {

            OrderEntity order = r.getData(new TypeReference<OrderEntity>() {});

            log.info("远程查询订单信息：{}", order);

            TypeReference<List<WareOrderTaskDetailEntity>> reference
                    = new TypeReference<List<WareOrderTaskDetailEntity>>() {};
            List<WareOrderTaskDetailEntity> entities = JacksonUtils.json2List(container, reference);

            if (Objects.isNull(order)) {
                // 说明订单在创建的时候失败了！
                this.batchUnlockSock(entities);
                return;
            }
            // 存在订单，则要验证是否已经关闭
            // 订单不是关闭状态，说明没必要解锁库存
            if (order.getStatus() != OrderStatusEnum.CANCEL.getCode()) {
                return ;
            }

            // 库存是否已经被解锁了
            List<Long> detailsIds = entities.stream()
                    .map(WareOrderTaskDetailEntity::getId).collect(Collectors.toList());
            List<WareOrderTaskDetailEntity> query = this.wareOrderTaskDetailService
                    .listByIds(detailsIds);
            // 如果已经被解锁了，那么久无需解锁
            query.removeIf(next -> next.getLockStatus() == WareConstant
                    .StockStatus.UNLOCKED.getCode());

            // 最终没有被解锁库存的工作单详情，才需要去解锁
            if (CollectionUtil.isNotEmpty(query)) {
                this.batchUnlockSock(query);
            }
            return;
        }

        log.info("订单号为[{}]远程调用查询失败，状态码为：{}", orderSnList, r.getCode());
        throw new UnlockStockException("订单号为[ " + orderSnList + " ]远程调用查询失败，状态码为：" + r.getCode());
    }



    @Override
    public void unlockStock(OrderEntity order) throws IOException {

        if (Objects.isNull(order) || StrUtil.isBlank(order.getOrderSn())) {
            log.error("订单实体：{}", order);
            throw new UnlockStockException("订单或订单号为空，无法解锁库存");
        }

        // 1. 获取到订单信息后，根据订单号，从数据库中查询出关于订单的信息
        List<WareOrderTaskDetailEntity> details = this.wareOrderTaskDetailService
                .selectTaskDetailsWithOrderSn (order.getOrderSn());

        // 直接解锁库存
        this.batchUnlockSock(details);
    }


    /**
     * 批量介所库存
     *
     * @param container     工作单详情容器
     */
    public void batchUnlockSock(List<WareOrderTaskDetailEntity> container) {

        // 这里由于 sql 比较调转，使用批量操作会报错！！！！！
        for (WareOrderTaskDetailEntity entity : container) {
            log.info("库存信息：{}", entity);
            this.baseMapper.updateBatchUnlockStock(entity);
        }

        // 修改所有得状态为解锁状态
        container = container.stream().map(
                a -> a.setLockStatus(WareConstant.StockStatus.UNLOCKED.getCode())
        ).collect(Collectors.toList());

        this.wareOrderTaskDetailService.updateBatchById(container);
    }
}